//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.InteropServices;
using STATSTG=System.Runtime.InteropServices.ComTypes.STATSTG;

namespace OpenLS.Spreadsheet.Formats.Biff
{
    internal sealed class Storage : IDisposable
    {
        private readonly List<string> _streamNames = new List<string>();
        private readonly List<string> _subStorageNames = new List<string>();
        private IStorage _storage;

        internal static Storage Open(string fileName)
        {
            if (!File.Exists(fileName))
                throw new FileNotFoundException(string.Empty, fileName);
            const bool bReadOnly = true;
            const STGM flags = (STGM.DIRECT_SWMR | STGM.SHARE_DENY_NONE);
            Storage result = new Storage();
               
            try
            {
                IStorage storage;
                int errorCode = API.StgOpenStorage(fileName, IntPtr.Zero, flags, IntPtr.Zero, 0, out storage);
                result._storage = storage;
                switch (errorCode)
                {
                    case -2147287007:
                    case -2147287008:
                        throw new ApplicationException();
                }
                if (errorCode != 0)
                {
                    throw new ApplicationException();
                }
                foreach (STATSTG e in getElements(storage))
                {
                    if (e.type == (int)STGTY.STGTY_STORAGE)
                        result._subStorageNames.Add(e.pwcsName);
                    if (e.type == (int)STGTY.STGTY_STREAM)
                        result._streamNames.Add(e.pwcsName);

                }
            }
            catch
            {
                result.close();
                throw;
            }
            return result;
            // object o = getElements();
        }

        private Storage()
        {
            
        }

        #region IDisposable Members

        public void Dispose()
        {
            close();
        }

        #endregion

        private IEnumerable<STATSTG> getElements()
        
        {
            IEnumSTATSTG ppenum = null;
            try
            {
                object o;
                _storage.EnumElements(0, IntPtr.Zero, 0, out o);
                ppenum = (IEnumSTATSTG)o;
                const int errorCode = 0;
                ppenum.Reset();
                STATSTG rgelt;
                uint pceltFetched;
                ppenum.Next(1, out rgelt, out pceltFetched);
                while ((errorCode == 0) && (1 == pceltFetched))
                {
                    yield return rgelt;
                    //\\  caller(rgelt, userData);
                    ppenum.Next(1, out rgelt, out pceltFetched);
                }
            }
            finally
            {
                if (ppenum != null)
                {
                    Marshal.ReleaseComObject(ppenum);
                    ppenum = null;
                }
            }
        }

        private static IEnumerable<STATSTG> getElements(IStorage _storage)
        {
            IEnumSTATSTG ppenum = null;
            try
            {
                object o;
                _storage.EnumElements(0, IntPtr.Zero, 0, out o);
                ppenum = (IEnumSTATSTG)o;
                const int errorCode = 0;
                ppenum.Reset();
                STATSTG rgelt;
                uint pceltFetched;
                ppenum.Next(1, out rgelt, out pceltFetched);
                while ((errorCode == 0) && (1 == pceltFetched))
                {
                    yield return rgelt;
                    //\\  caller(rgelt, userData);
                    ppenum.Next(1, out rgelt, out pceltFetched);
                }
            }
            finally
            {
                if (ppenum != null)
                {
                    Marshal.ReleaseComObject(ppenum);
                    ppenum = null;
                }
            }
        }


        public Stream OpenStream(string streamName, FileAccess access)
        {
            IStream stream = _storage.OpenStream(streamName, IntPtr.Zero, (int) (STGM.SHARE_EXCLUSIVE), 0);

            return new CFStream(stream, access);
        }

        private void close()
        {
            if (_storage != null)
            {
                Marshal.ReleaseComObject(_storage);
                _storage = null;
            }
        }

        public static Storage Create(string fileName)
        {
            Contract.Ensures(Contract.Result<Storage>() != null);
            Contract.Ensures(Contract.Result<Storage>()._storage != null);
            Storage result = new Storage();

            Guid iid = new Guid("0000000B-0000-0000-C000-000000000046");
            object storage;
            STGM stgm = STGM.CREATE | STGM.READWRITE | STGM.SHARE_EXCLUSIVE;
            var apiResult = API.StgCreateStorageEx(fileName, stgm, STGFMT.STORAGE, 0, IntPtr.Zero, IntPtr.Zero, ref iid, out storage);
            result._storage = (IStorage) storage;
            return result;
        }

        public Stream CreateStream(string streamName, FileAccess access)
        {
            IStream stream = _storage.CreateStream(streamName, (int) STGM.SHARE_EXCLUSIVE | (int)STGM.READWRITE, 0, 0);
            var result =  new CFStream(stream, access);
            return result;
        }
    }
}